Skip to contentMethod: cloneIdentifier(Object, Object, EntityType)
1: /**
2: * Copyright (C) 2020 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.sessions;
16:
17: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
19: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
20: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
21: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
22: import cz.cvut.kbss.jopa.model.metamodel.Identifier;
23: import cz.cvut.kbss.jopa.model.metamodel.Metamodel;
24: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
25: import org.slf4j.Logger;
26: import org.slf4j.LoggerFactory;
27:
28: import java.lang.reflect.Field;
29: import java.net.URI;
30: import java.net.URL;
31: import java.time.Instant;
32: import java.time.LocalDate;
33: import java.time.LocalDateTime;
34: import java.time.ZonedDateTime;
35: import java.util.*;
36:
37: public class CloneBuilderImpl implements CloneBuilder {
38:
39: private static final Logger LOG = LoggerFactory.getLogger(CloneBuilderImpl.class);
40:
41: private static final Set<Class<?>> IMMUTABLE_TYPES = getImmutableTypes();
42:
43: // Contains entities that are already cloned, so that we don't clone them again
44: private final RepositoryMap visitedEntities;
45:
46: private final Builders builders;
47:
48: private final UnitOfWorkImpl uow;
49:
50: public CloneBuilderImpl(UnitOfWorkImpl uow) {
51: this.uow = uow;
52: this.visitedEntities = new RepositoryMap();
53: this.builders = new Builders();
54: }
55:
56: @Override
57: public Object buildClone(Object original, CloneConfiguration cloneConfiguration) {
58: Objects.requireNonNull(original);
59: Objects.requireNonNull(cloneConfiguration);
60: // TODO Replace with a lambda after migration to new SLF4J
61: LOG.trace("Cloning object {}.", stringify(original));
62: return buildCloneImpl(null, null, original, cloneConfiguration);
63: }
64:
65: @Override
66: public Object buildClone(Object cloneOwner, Field clonedField, Object original, Descriptor descriptor) {
67: if (cloneOwner == null || original == null || descriptor == null) {
68: throw new NullPointerException();
69: }
70: // TODO Replace with a lambda after migration to new SLF4J
71: LOG.trace("Cloning object {} with owner {}", stringify(original), stringify(cloneOwner));
72: return buildCloneImpl(cloneOwner, clonedField, original, new CloneConfiguration(descriptor));
73: }
74:
75: private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object original,
76: CloneConfiguration cloneConfiguration) {
77: assert original != null;
78: if (isOriginalInUoW(original)) {
79: return uow.getCloneForOriginal(original);
80: }
81: final Class<?> cls = original.getClass();
82: final boolean managed = isTypeManaged(cls);
83: final Descriptor descriptor = cloneConfiguration.getDescriptor();
84: if (managed) {
85: final Object visitedClone = getVisitedEntity(descriptor, original);
86: if (visitedClone != null) {
87: return visitedClone;
88: }
89: }
90: final AbstractInstanceBuilder builder = getInstanceBuilder(original);
91: Object clone = builder.buildClone(cloneOwner, clonedField, original, cloneConfiguration);
92: if (managed) {
93: // Register visited object before populating attributes to prevent endless cloning cycles
94: putVisitedEntity(descriptor, original, clone);
95: }
96: if (!builder.populatesAttributes() && !isImmutable(cls)) {
97: populateAttributes(original, clone, cloneConfiguration);
98: }
99: return clone;
100: }
101:
102: /**
103: * Clone all the attributes of the original and set the clone values. This also means cloning any relationships and
104: * their targets.
105: */
106: private void populateAttributes(Object original, Object clone, CloneConfiguration configuration) {
107: final Class<?> originalClass = original.getClass();
108: final EntityType<?> et = getMetamodel().entity(originalClass);
109: // Ensure the identifier is cloned before any other attributes
110: // This prevents problems where circular references between entities lead to clones being registered with null identifier
111: cloneIdentifier(original, clone, et);
112: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
113: if (fs == et.getIdentifier()) {
114: continue; // Already cloned
115: }
116: final Field f = fs.getJavaField();
117: final Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
118: if (origVal == null) {
119: continue;
120: }
121: final Class<?> origValueClass = origVal.getClass();
122: Object clonedValue;
123: if (isImmutable(origValueClass)) {
124: // The field is an immutable type
125: clonedValue = origVal;
126: } else if (origVal instanceof Collection || origVal instanceof Map) {
127: final Descriptor fieldDescriptor = getFieldDescriptor(f, originalClass, configuration.getDescriptor());
128: // Collection or Map
129: clonedValue = getInstanceBuilder(origVal).buildClone(clone, f, origVal,
130: new CloneConfiguration(fieldDescriptor, configuration.getPostRegister()));
131: } else {
132: // Otherwise we have a relationship and we need to clone its target as well
133: if (isOriginalInUoW(origVal)) {
134: // If the reference is already managed
135: clonedValue = uow.getCloneForOriginal(origVal);
136: } else {
137: if (isTypeManaged(origValueClass)) {
138: final Descriptor fieldDescriptor =
139: getFieldDescriptor(f, originalClass, configuration.getDescriptor());
140: clonedValue = getVisitedEntity(configuration.getDescriptor(), origVal);
141: if (clonedValue == null) {
142: clonedValue = uow.registerExistingObject(origVal, fieldDescriptor,
143: configuration.getPostRegister());
144: }
145: } else {
146: clonedValue = buildClone(origVal, configuration);
147: }
148: }
149: }
150: EntityPropertiesUtils.setFieldValue(f, clone, clonedValue);
151: }
152: }
153:
154: private static void cloneIdentifier(Object original, Object clone, EntityType<?> et) {
155: final Identifier identifier = et.getIdentifier();
156: final Object idValue = EntityPropertiesUtils.getFieldValue(identifier.getJavaField(), original);
157: EntityPropertiesUtils.setFieldValue(identifier.getJavaField(), clone, idValue);
158: }
159:
160: private Descriptor getFieldDescriptor(Field field, Class<?> entityClass, Descriptor entityDescriptor) {
161: final EntityType<?> et = getMetamodel().entity(entityClass);
162: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(field.getName());
163: return entityDescriptor.getAttributeDescriptor(fieldSpec);
164: }
165:
166: /**
167: * Check if the given class is an immutable type.
168: * <p>
169: * Objects of immutable types do not have to be cloned, because they cannot be modified.
170: * <p>
171: * Note that this method does not do any sophisticated verification, it just checks if the specified class
172: * corresponds to a small set of predefined conditions, e.g. primitive class, enum, String.
173: *
174: * @param cls the class to check
175: * @return Whether the class represents immutable objects
176: */
177: static boolean isImmutable(Class<?> cls) {
178: return cls.isPrimitive() || cls.isEnum() || IMMUTABLE_TYPES.contains(cls);
179: }
180:
181: /**
182: * Checks if the specified object is immutable.
183: * <p>
184: * {@code null} is considered immutable, otherwise, this method just calls {@link #isImmutable(Class)}.
185: *
186: * @param object The instance to check
187: * @return immutability status
188: */
189: static boolean isImmutable(Object object) {
190: return object == null || isImmutable(object.getClass());
191: }
192:
193: @Override
194: public void mergeChanges(ObjectChangeSet changeSet) {
195: final Object original = changeSet.getChangedObject();
196: try {
197: for (ChangeRecord change : changeSet.getChanges()) {
198: Field f = change.getAttribute().getJavaField();
199: if (isImmutable(f.getType())) {
200: EntityPropertiesUtils.setFieldValue(f, original, change.getNewValue());
201: continue;
202: }
203: Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
204: Object newVal = change.getNewValue();
205: if (newVal == null) {
206: EntityPropertiesUtils.setFieldValue(f, original, null);
207: continue;
208: }
209: getInstanceBuilder(newVal).mergeChanges(f, original, origVal, newVal);
210: }
211: } catch (SecurityException e) {
212: throw new OWLPersistenceException(e);
213: }
214: }
215:
216: private Object getVisitedEntity(Descriptor descriptor, Object original) {
217: assert descriptor != null;
218: assert original != null;
219: return visitedEntities.get(descriptor, original);
220: }
221:
222: private void putVisitedEntity(Descriptor descriptor, Object original, Object clone) {
223: assert descriptor != null;
224: visitedEntities.add(descriptor, original, clone);
225: }
226:
227: AbstractInstanceBuilder getInstanceBuilder(Object toClone) {
228: return builders.getBuilder(toClone);
229: }
230:
231: boolean isTypeManaged(Class<?> cls) {
232: return uow.isEntityType(cls);
233: }
234:
235: boolean isOriginalInUoW(Object original) {
236: return uow.containsOriginal(original);
237: }
238:
239: Object getOriginal(Object clone) {
240: return uow.getOriginal(clone);
241: }
242:
243: Metamodel getMetamodel() {
244: return uow.getMetamodel();
245: }
246:
247: @Override
248: public void reset() {
249: visitedEntities.clear();
250: }
251:
252: @Override
253: public void removeVisited(Object instance, Descriptor descriptor) {
254: visitedEntities.remove(descriptor, instance);
255: }
256:
257: IndirectCollection<?> createIndirectCollection(Object c, Object owner, Field f) {
258: return uow.createIndirectCollection(c, owner, f);
259: }
260:
261: /**
262: * Gets basic object info for logging.
263: * <p>
264: * This works around using {@link Object#toString()} for entities, which could inadvertently trigger lazy field
265: * fetching.
266: *
267: * @param object Object to stringify
268: * @return String info about the specified object
269: */
270: private String stringify(Object object) {
271: assert object != null;
272: return isTypeManaged(object.getClass()) ?
273: object.getClass().getSimpleName() + "<" + EntityPropertiesUtils.getIdentifier(object, getMetamodel()) +
274: ">" : object.toString();
275: }
276:
277: private static Set<Class<?>> getImmutableTypes() {
278: HashSet<Class<?>> ret = new HashSet<>();
279: ret.add(Boolean.class);
280: ret.add(Character.class);
281: ret.add(Byte.class);
282: ret.add(Short.class);
283: ret.add(Integer.class);
284: ret.add(Long.class);
285: ret.add(Float.class);
286: ret.add(Double.class);
287: ret.add(Void.class);
288: ret.add(String.class);
289: ret.add(URI.class);
290: ret.add(URL.class);
291: ret.add(LocalDate.class);
292: ret.add(LocalDateTime.class);
293: ret.add(ZonedDateTime.class);
294: ret.add(Instant.class);
295: return ret;
296: }
297:
298: private final class Builders {
299: private AbstractInstanceBuilder defaultBuilder;
300: private AbstractInstanceBuilder dateBuilder;
301: // Lists and Sets
302: private AbstractInstanceBuilder collectionBuilder;
303: private AbstractInstanceBuilder mapBuilder;
304:
305: private Builders() {
306: this.defaultBuilder = new DefaultInstanceBuilder(CloneBuilderImpl.this, uow);
307: this.dateBuilder = new DateInstanceBuilder(CloneBuilderImpl.this, uow);
308: }
309:
310: private AbstractInstanceBuilder getBuilder(Object toClone) {
311: if (toClone instanceof Date) {
312: return dateBuilder;
313: }
314: if (toClone instanceof Map) {
315: if (mapBuilder == null) {
316: this.mapBuilder = new MapInstanceBuilder(CloneBuilderImpl.this, uow);
317: }
318: return mapBuilder;
319: } else if (toClone instanceof Collection) {
320: if (collectionBuilder == null) {
321: this.collectionBuilder = new CollectionInstanceBuilder(CloneBuilderImpl.this, uow);
322: }
323: return collectionBuilder;
324: } else {
325: return defaultBuilder;
326: }
327: }
328: }
329: }